﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MVC = System.Web.Mvc;
using System.Web.Mvc;
using System.Web;
using Lilium.Web.Mvc.Metadata;

namespace Lilium.Web.Mvc.Security
{
    [AttributeUsage(AttributeTargets.Class)]
    public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
    {
        #region IAuthorizationFilter Members

        public void OnAuthorization(MVC.AuthorizationContext filterContext)
        {
            if (filterContext == null) throw new ArgumentNullException("filterContext");

            var controller = filterContext.Controller;
            var actionDescriptor = filterContext.ActionDescriptor;

            var authorizationSrvice = GetAuthorizationSrvice(controller);
            var metadataService = controller.GetService<IMetadataService>(true);

            var actionContext = new ActionContext(
                metadataService.GetControllerArea(actionDescriptor.ControllerDescriptor),
                actionDescriptor.ControllerDescriptor.ControllerName,
                actionDescriptor.ActionName,
                filterContext.RouteData.Values);

            var authorizationContext = new AuthorizationContext(filterContext.HttpContext.User, actionContext);

            if (authorizationSrvice.Authorize(authorizationContext))
            {
                var cache = filterContext.HttpContext.Response.Cache;
                cache.SetProxyMaxAge(new TimeSpan(0));
                cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidateHandler), Tuple.Create(controller, actionContext));
            }
            else
            {
                filterContext.Result = GetUnauthorizedResult(controller);
                foreach (var pair in authorizationContext.RedirectData)
                    controller.TempData[pair.Key] = pair.Value;
            }
        }

        #endregion

        #region public Type AuthorizationService

        private Type m_AuthorizationService;

        public Type AuthorizationService
        {
            get
            {
                return m_AuthorizationService;
            }
            set
            {
                m_AuthorizationService = value;
            }
        }

        #endregion

        #region public Type UnauthorizedResult

        private Type m_UnauthorizedResult;

        public Type UnauthorizedResult
        {
            get { return m_UnauthorizedResult; }
            set { m_UnauthorizedResult = value; }
        }

        #endregion

        protected virtual IAuthorizationService GetAuthorizationSrvice(ControllerBase controller)
        {
            if (controller == null) throw new ArgumentNullException("controller");

            if (AuthorizationService != null)
                return (IAuthorizationService)controller.GetService(AuthorizationService, true);
            else
                return controller.GetService<IAuthorizationService>(true);
        }

        protected virtual ActionResult GetUnauthorizedResult(ControllerBase controller)
        {
            if (controller == null) throw new ArgumentNullException("controller");

            if (UnauthorizedResult != null)
                return (ActionResult)controller.GetService(UnauthorizedResult, true);
            else
                return controller.GetService<HttpUnauthorizedResult>(true);
        }

        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            if (context == null) throw new ArgumentNullException("context");
            if (data == null) throw new ArgumentNullException("data");

            var parameters = (Tuple<ControllerBase, ActionContext>)data;

            var authorizationSrvice = GetAuthorizationSrvice(parameters.Item1);
            if (authorizationSrvice.Authorize(new AuthorizationContext(context.User, parameters.Item2)))
                validationStatus = HttpValidationStatus.Valid;
            else
                validationStatus = HttpValidationStatus.IgnoreThisRequest;
        }
    }
}